package uk.ac.sanger.das.dasregistry.springrest.sources;

import java.math.BigInteger;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.log4j.Logger;
import org.biodas.jdas.dassources.Capabilities;
import org.biodas.jdas.dassources.utils.RegistrySourceAdapter;
import org.biodas.jdas.dassources.utils.SourcesFilter;
import org.biodas.jdas.exceptions.ValidationException;
import org.biodas.jdas.schema.lastmodified.LASTMODIFIED;
import org.biodas.jdas.schema.registry.capabilities.CAPABILITIES;
import org.biodas.jdas.schema.registry.capabilities.CAPABILITY;
import org.biodas.jdas.schema.registry.capabilities.ObjectFactory;
import org.biodas.jdas.schema.servers.SERVER;
import org.biodas.jdas.schema.servers.SERVERS;
import org.biodas.jdas.schema.sources.PROP;
import org.biodas.jdas.schema.sources.SOURCE;
import org.biodas.jdas.schema.sources.SOURCES;
import org.biodas.jdas.validation.RegistrySourceValidator;


import uk.ac.sanger.das.dasregistry.springrest.sources.DasSourceDao;

public class DasSourceManagerImp extends Thread implements DasSourceManager {

	DasSourceDao dasSourceDao;
	private static Logger logger = Logger.getLogger(DasSourceManager.class);
	Date lastUpdate = null;// last time data was updated
	SourcesCache sourcesCache;

	public DasSourceManagerImp() {
	}

	public DasSourceManagerImp(DasSourceDao dasSourceDao) {
		this.dasSourceDao = dasSourceDao;
		this.sourcesCache = new SourcesCache(dasSourceDao);
		sourcesCache.start();
	}

	/**
	 * get sources from database and handle caching
	 */
	public SOURCES getAllSources() {
		return sourcesCache.getAllSources();
	}

	public SOURCES add(SOURCES sources) throws DasSourceNotRegisterableExcepton {
		RegistrySourceValidator validator = new RegistrySourceValidator();
		logger.debug("got request for adding a Sources");

		if (sources.getSOURCE().size() == 0 || sources.getSOURCE().size() > 1) {
			throw new DasSourceNotRegisterableExcepton(
					"we only accept posts for one source at the moment");
		}
		SOURCE source = sources.getSOURCE().get(0);
		logger.debug("invalid in dassourcemanager " + source.getUri());
		// validate the source as it's been requested if not conform then don't
		// add
		if (!isValid(source, validator)) {
			throw new DasSourceNotRegisterableExcepton(validator.getMessage());
		}
		// does this source already exist
		String regId = null;
		try {
			regId = validator.getRegistryId(source, this.getAllSources()
					.getSOURCE());
		} catch (ValidationException e) {
			//  Auto-generated catch block
			e.printStackTrace();
		}
		if (!"".equals(regId)) {
			throw new DasSourceNotRegisterableExcepton("source exists already "
					+ regId);
		}

		logger.debug("returning created sources");

		SOURCES addedSources = null;
		addedSources = dasSourceDao.add(sources);
		sourcesCache.add(addedSources);
		return addedSources;

	}

	private boolean isValid(SOURCE source, RegistrySourceValidator validator) {
		boolean valid = validator.validate(source);
		String errors = validator.getMessage();
		if (!errors.equals("")) {
			logger.debug("errors=" + errors);
			return false;
		}
		return true;

	}

	@Override
	public void activate(String sourceUri) {
		logger.debug("Request to activate source with Uri=" + sourceUri);
		dasSourceDao.activate(sourceUri);
		sourcesCache.activate(sourceUri);

	}

	@Override
	public void delete(String sourceUri) {
		logger.debug("got a request to delete a source ");
		int rowsAffected = dasSourceDao.delete(sourceUri);
		logger.warn("deleted rows affected=" + rowsAffected);
		if (rowsAffected == 1){
			sourcesCache.delete(sourceUri);
		}
		return;
	}

	@Override
	/**
	 * update a specific source (not refresh the cache use other methods)
	 */
	public SOURCES update(SOURCES sources) {
		SOURCES returnedSource = dasSourceDao.update(sources);
		if((returnedSource!=null)&&(returnedSource.getSOURCE().size()!=0))sourcesCache.update(returnedSource);
		return returnedSource;
	}

	@Override
	public LASTMODIFIED getLastModified() {
		Date date = dasSourceDao.getLastModified();
		GregorianCalendar c = new GregorianCalendar();
		c.setTime(date);
		XMLGregorianCalendar date2 = null;
		try {
			date2 = DatatypeFactory.newInstance().newXMLGregorianCalendar(c);
		} catch (DatatypeConfigurationException e) {
			//  Auto-generated catch block
			e.printStackTrace();
		}
		LASTMODIFIED mod = new LASTMODIFIED();
		mod.setTime(date2);
		return mod;

	}

	@Override
	public SERVERS getServers() {
		// can do this for some registry instances by looping over the sources
	//want to get all sources/server uris even if not in server table
		//changed this method so we always have up to date stats and dont have to depend on a chron job
		//just using this to get the list of servers from the database that are server controlled
		SERVERS serversInDb= dasSourceDao.getServers();
		Map<String, SERVER> serverMap=new HashMap<String, SERVER>();
		for(SERVER server:serversInDb.getSERVER()){
			String url=server.getUri();
			//System.out.println("server url="+url);
			//set all the numbers to zero as we are calculating them again - can get rid of number_of_sources field in server table! 
			server.setNumberOfSources(BigInteger.ZERO);
			serverMap.put(url, server);
		}
		SOURCES sources = this.getAllSources();
		for(SOURCE source:sources.getSOURCE()){
			List<PROP> props = source.getVERSION().get(0).getPROP();
			boolean active=true;
			//if inactive ignore source for server stats
			for(PROP prop:props){
				if(prop.getName().equals("inactive"))active=false;
			}
			
			if(active){
			
			RegistrySourceAdapter ra=new RegistrySourceAdapter(source);
			
			String sourcesDoc=ra.getSourcesDoc();
			if(serverMap.containsKey(sourcesDoc)){
				SERVER tempServer=serverMap.get(sourcesDoc);
				BigInteger numberOfSources = tempServer.getNumberOfSources();
				BigInteger plusOne=numberOfSources.add(BigInteger.valueOf(1));
				tempServer.setNumberOfSources(plusOne);
			}else{
				//not exsiting in map already so create a new server entry
				SERVER newServer=new SERVER();
				newServer.setServerControlled("no");
				newServer.setDescription("");
				newServer.setUri(sourcesDoc);
				newServer.setNumberOfSources(BigInteger.valueOf(1));
				serverMap.put(sourcesDoc, newServer);
			}
			
		}
		}
		
		SERVERS servers=new SERVERS();
		servers.getSERVER().addAll(serverMap.values());
		return servers;//dasSourceDao.getServers();
	}

	private void updateCache(SOURCE source, String url) {
		// if we have updated it can i access the current sources cache and
		// update the single source?
		for (SOURCE cacheSource : sourcesCache.getAllSources().getSOURCE()) {
			RegistrySourceAdapter sA = new RegistrySourceAdapter(cacheSource);
			String cachUrl = null;
			try {
				cachUrl = sA.getOriginalDataSourceUri() + "/";
			} catch (ValidationException e) {
				//  Auto-generated catch block
				e.printStackTrace();
			}
			if (cachUrl.equals(url)) {
				System.out.println("===============data source found========"
						+ url);
				cacheSource = source;
			}
		}
	}

	@Override
	public CAPABILITIES getKnownCapabilities() {
		ObjectFactory factory=new ObjectFactory();
		CAPABILITIES caps=factory.createCAPABILITIES();
		
		for(Capabilities cap:Capabilities.values()){
			String name=cap.getName();
			CAPABILITY newCap=new CAPABILITY();
			newCap.setType("das1:"+name);
			caps.getCAPABILITY().add(newCap);
		}
		return caps;
	}

}
